﻿using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Net;
using System.Threading;
namespace SGLib.Net
{

    public class ClientSession
    {
        NetClient mNetClient;
        Socket ClientSocket;

        /// <summary>
        /// 默认的收包Buff尺寸 用于断包或者粘包缓存
        /// </summary>
        const int DefaultReceiveBufferSize = 4096 * 200;
        byte[] RecvBuffer = new byte[DefaultReceiveBufferSize];
        /// <summary>
        /// 当前收包缓冲区数据大小
        /// </summary>
        int RecvBufferSize = 0;
        /// <summary>
        /// 数据缓冲区内的包的实际大小
        /// </summary>
        int RecvCurPacketSize = -1;
        /// <summary>
        /// 是否连接中
        /// </summary>
        bool m_InConnecting = false;

        public event EventHandler Connected;
        public event EventHandler<DataEventArgs> DataReceived;
        public event Action<string> NetErrorEvent;

        protected ArraySegment<byte> Buffer { get; set; }
        private SocketAsyncEventArgs m_SocketEventArgs;
        private SocketAsyncEventArgs m_SocketEventArgsSend;


        private ConcurrentBatchQueue<ArraySegment<byte>> m_SendingQueue;
        public int SendingQueueSize { get; set; }
        private int m_IsSending = 0;

        public ClientSession(NetClient client)
        {
            this.mNetClient = client;
        }


        public void Connect(EndPoint endPoint)
        {
            if (m_InConnecting || this.ClientSocket != null)
            {
                this.NetErrorEvent("套接字正在连接中, 不允许重复连接!");
                return;
            }
            this.m_InConnecting = true;
            this.ConnectAsync(endPoint, this.ProcessConnect);
        }

        void ConnectAsync(EndPoint ipEndPoint, Action<Socket, SocketAsyncEventArgs> callBack)
        {
            var e = this.CreateSocketAsyncEventArgs(ipEndPoint, callBack);
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socket.ConnectAsync(e);
        }

        SocketAsyncEventArgs CreateSocketAsyncEventArgs(EndPoint ipEndPoint, Action<Socket, SocketAsyncEventArgs> callBack)
        {
            SocketAsyncEventArgs NewE = new SocketAsyncEventArgs();
            NewE.UserToken = callBack;
            NewE.RemoteEndPoint = ipEndPoint;
            NewE.Completed += new EventHandler<SocketAsyncEventArgs>(SocketAsyncEventCompleted);
            return NewE;
        }
        private void SocketAsyncEventCompleted(object sender, SocketAsyncEventArgs e)
        {
            e.Completed -= SocketAsyncEventCompleted;
            Action<Socket, SocketAsyncEventArgs> callBack = (Action<Socket, SocketAsyncEventArgs>)e.UserToken;
            e.UserToken = null;
            callBack(sender as Socket, e);
        }
        void ProcessConnect(Socket socket, SocketAsyncEventArgs e)
        {
            this.m_InConnecting = false;
            this.ClientSocket = socket;
            if (!socket.Connected)
            {
                this.m_InConnecting = false;
                this.NetErrorEvent("连接失败" + e.SocketError.ToString());
                return;
            }
            if (this.Buffer.Array == null)
            {
                Buffer = new ArraySegment<byte>(new byte[DefaultReceiveBufferSize]);
            }
            e.SetBuffer(Buffer.Array, Buffer.Offset, Buffer.Count);
            m_SocketEventArgs = e;
            this.m_SocketEventArgs.Completed += SocketEventArgsCompleted;
            this.StartReceive();
            if (this.Connected != null)
            {
                this.Connected(this, EventArgs.Empty);
            }
        }

        private void SocketEventArgsCompleted(object sender, SocketAsyncEventArgs e)
        {
            if (e.LastOperation == SocketAsyncOperation.Connect)
            {
                ProcessConnect(sender as Socket, e);
                return;
            }
            ProcessReceive(e);
        }

        void StartReceive()
        {
            try
            {
                this.ClientSocket.ReceiveAsync(this.m_SocketEventArgs);
            }
            catch (SocketException exc)
            {
                this.NetErrorEvent(exc.ToString());
                this.ClientSocket.Shutdown(SocketShutdown.Both);
            }
            catch (Exception e)
            {
                this.NetErrorEvent(e.ToString());
                this.ClientSocket.Shutdown(SocketShutdown.Both);
            }
        }
        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            if (e.SocketError != SocketError.Success || e.BytesTransferred == 0)
            {
                this.NetErrorEvent("连接断开 " + e.SocketError.ToString());
                this.ClientSocket.Shutdown(SocketShutdown.Both);
                return;
            }

            OnDataReceived(e.Buffer, e.Offset, e.BytesTransferred);
            StartReceive();
        }

        private DataEventArgs m_DataArgs = new DataEventArgs();
        protected virtual void OnDataReceived(byte[] data, int offset, int length)
        {
            if (offset != 0)
            {
                this.NetErrorEvent("收包Offset不为0");
                return;
            }
         
            m_DataArgs.Data = data;
            m_DataArgs.Offset = offset;
            m_DataArgs.Length = length;
            int recvSize = this.mNetClient.Codec.GetPackageSize(data, offset);            
            //如果没有缓冲数据并且当前包大小和规定大小一致
            if (length == recvSize && this.RecvCurPacketSize == -1)
            {
                if (DataReceived != null)
                {
                    this.DataReceived(this, this.m_DataArgs);
                }
            }
            //粘包&断包问题
            else
            {
                System.Array.Copy(data,offset,this.RecvBuffer, this.RecvBufferSize, length);
                this.RecvBufferSize += length;
                this.processRecvBuffer();
            }

        }
        void processRecvBuffer()
        {
            if (this.RecvBufferSize < 4)
            {
                return;
            }
            int pkgSize = this.mNetClient.Codec.GetPackageSize(this.RecvBuffer, 0);
            if (this.RecvBufferSize >= pkgSize)
            {
                m_DataArgs.Data = this.RecvBuffer;
                m_DataArgs.Offset = 0;
                m_DataArgs.Length = pkgSize;
                if (DataReceived != null)
                {
                    this.DataReceived(this, this.m_DataArgs);
                }
                int nextSize = this.RecvBufferSize - pkgSize;
                System.Array.Copy(this.RecvBuffer, pkgSize, this.RecvBuffer,0, nextSize);
                System.Array.Clear(this.RecvBuffer, nextSize, this.RecvBufferSize - nextSize);
                this.RecvBufferSize -= pkgSize;
                this.processRecvBuffer();
            }

        }

        public void Close()
        {
            if (this.ClientSocket != null && this.ClientSocket.Connected)
            {
                this.ClientSocket.Shutdown(SocketShutdown.Both);
                this.ClientSocket.Close();
            }
            if (this.m_SocketEventArgs != null)
            {
                this.m_SocketEventArgs.Dispose();
            }
        }

        #region Send
        public void Send(ArraySegment<byte> segment)
        {
            if (segment == null || segment.Count == 0)
            {
                this.NetErrorEvent("发送字节不能为空");
                return;
            }
            if (TrySend(segment))
            {
                return;
            }
            while (true)
            {
                Thread.SpinWait(1);
                if (TrySend(segment))
                {
                    return;
                }
            }
        }
        public bool TrySend(ArraySegment<byte> segment)
        {
            bool isEnqueued = GetSendingQueue().Enqueue(segment);
            if (Interlocked.CompareExchange(ref m_IsSending, 1, 0) != 0)
            {
                return isEnqueued;
            }

            this.DequeueSend();
            return isEnqueued;
        }
        private ConcurrentBatchQueue<ArraySegment<byte>> GetSendingQueue()
        {
            if (this.m_SendingQueue != null)
            {
                return m_SendingQueue;
            }
            lock (this)
            {
                if (m_SendingQueue != null)
                {
                    return m_SendingQueue;
                }
                m_SendingQueue = new ConcurrentBatchQueue<ArraySegment<byte>>(Math.Max(SendingQueueSize, 1024), (t) => t.Array == null || t.Count == 0);
                return m_SendingQueue;
            }
        }
        private void DequeueSend()
        {
            PosList<ArraySegment<byte>> sendingItems = GetSendingItems();
            if (!m_SendingQueue.TryDequeue(sendingItems))
            {
                m_IsSending = 0;
                return;
            }
            this.SendInternal(sendingItems);
        }
        private PosList<ArraySegment<byte>> m_SendingItems;
        private PosList<ArraySegment<byte>> GetSendingItems()
        {
            if (m_SendingItems == null)
                m_SendingItems = new PosList<ArraySegment<byte>>();

            return m_SendingItems;
        }

        protected void SendInternal(PosList<ArraySegment<byte>> items)
        {
            if (m_SocketEventArgsSend == null)
            {
                m_SocketEventArgsSend = new SocketAsyncEventArgs();
                m_SocketEventArgsSend.Completed += new EventHandler<SocketAsyncEventArgs>(Sending_Completed);
            }
            if (items.Count > 1)
            {
                m_SocketEventArgsSend.SetBuffer(null, 0, 0);
                m_SocketEventArgsSend.BufferList = items;
            }
            else
            {
                m_SocketEventArgsSend.BufferList = null;
                ArraySegment<byte> currentItem = items[0];
                try
                {
                    m_SocketEventArgsSend.SetBuffer(currentItem.Array, 0, currentItem.Count);
                }
                catch (System.Exception e)
                {
                    this.NetErrorEvent(e.ToString());
                }
            }
            bool raiseEvent = ClientSocket.SendAsync(m_SocketEventArgsSend);
            if (!raiseEvent)
            {
                Sending_Completed(ClientSocket, m_SocketEventArgsSend);
            }
        }
        void Sending_Completed(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError != SocketError.Success || e.BytesTransferred == 0)
            {
                this.NetErrorEvent("发包失败" + e.SocketError.ToString());
                this.Close();
                return;
            }
            this.OnSendingCompleted();
        }
        protected void OnSendingCompleted()
        {
            var sendingItems = GetSendingItems();
            sendingItems.Clear();
            sendingItems.Position = 0;

            if (!m_SendingQueue.TryDequeue(sendingItems))
            {
                m_IsSending = 0;
                return;
            }
            SendInternal(sendingItems);
        }
        #endregion
    }

    public class DataEventArgs : EventArgs
    {
        public byte[] Data { get; set; }
        public int Offset { get; set; }
        public int Length { get; set; }
    }
}
